package de.bloxel.world;

import java.util.ArrayList;
import java.util.List;

import com.google.common.collect.Lists;
import com.jme3.bounding.BoundingBox;
import com.jme3.math.Vector3f;

import de.bloxel.core.BloxelFactory;
import de.bloxel.core.BloxelFactory.BloxelType;

/**
 * Try different performance tweaks.
 * 
 * <ul>
 * <li>store {@link TerrainElement TerrainElements} in tree-dimensional array - no more contains tests with
 * {@link BoundingBox}</li>
 * <li>self growing {@link TerrainElement TerrainElements} - only create them if they are needed</li>
 * <li>growing horizon to speed up the first frames</li>
 * </ul>
 * 
 * @author Klaus Hauschild
 * @since 0.1.0
 */
public class TerrainChunkImpl2 implements TerrainChunk {

  static class EmptyTerrainElement extends TerrainElement {
    public EmptyTerrainElement() {
      super(Vector3f.ZERO, Vector3f.ZERO, null);
    }
  }

  private static final int CHUNK_SIZE = 128;
  private static final int HALF_CHUNK_SIZE = CHUNK_SIZE / 2;
  private static final int START_HORIZON = 18;
  private static final int END_HORIZON = 30;
  private static final TerrainElement EMPTY_TERRAIN_ELEMENT = new EmptyTerrainElement();

  private float horizon = START_HORIZON;
  private int elementCount = 0;
  private final Vector3f center;

  /**
   * terrainElements[x][y][z]
   */
  private final TerrainElement[][][] terrainElements = new TerrainElement[CHUNK_SIZE][CHUNK_SIZE][CHUNK_SIZE];

  public TerrainChunkImpl2(final TerrainLoader theLoader, final int theEventHorizon) {
    this(Vector3f.ZERO, theLoader, theEventHorizon);
  }

  public TerrainChunkImpl2(final Vector3f theCenter, final TerrainLoader theLoader, final float theEventHorizon) {
    center = theCenter;
    theLoader.fill(this);
  }

  @Override
  public void addTerrainElement(final TerrainElement aTerrainElement) {
    final Vector3f theTargetPosition = aTerrainElement.getCenter();
    terrainElements[(int) theTargetPosition.x + HALF_CHUNK_SIZE][(int) theTargetPosition.y + HALF_CHUNK_SIZE][(int) theTargetPosition.z
        + HALF_CHUNK_SIZE] = aTerrainElement;
  }

  @Override
  public void addTerrainElement(final Vector3f theTargetPosition, final BloxelType theBoxelType) {
    elementCount++;
    terrainElements[(int) theTargetPosition.x + HALF_CHUNK_SIZE][(int) theTargetPosition.y + HALF_CHUNK_SIZE][(int) theTargetPosition.z
        + HALF_CHUNK_SIZE] = new TerrainElement(theTargetPosition,
        Vector3f.UNIT_XYZ.mult(BloxelFactory.DEFAULT_BLOXEL_DIMENSION), theBoxelType);
  }

  private boolean checkBorders(final int theX, final int theY, final int theZ) {
    return theX >= 0 && theX < CHUNK_SIZE && theY >= 0 && theY < CHUNK_SIZE && theZ >= 0 && theZ < CHUNK_SIZE;
  }

  private TerrainElement createElement(final int theX, final int theY, final int theZ) {
    if (theY != 0) {
      return EMPTY_TERRAIN_ELEMENT;
    }
    elementCount++;
    return new TerrainElement(new Vector3f(theX, theY, theZ),
        Vector3f.UNIT_XYZ.mult(BloxelFactory.DEFAULT_BLOXEL_DIMENSION), BloxelType.GRASS, true, true);
  }

  @Override
  public BoundingBox getBoundingBox() {
    // TODO Auto-generated method stub
    return null;
  }

  @Override
  public Vector3f getCenter() {
    return center;
  }

  private int getHorizon() {
    return (int) horizon;
  }

  @Override
  public int getNumberOfElements() {
    return elementCount;
  }

  @Override
  public List<TerrainElement> getTerrainElements() {
    // TODO
    return Lists.newArrayList();
  }

  @Override
  public List<TerrainElement> getTerrainElements(final Vector3f thePlayerLocation) {
    return getTerrainElements(thePlayerLocation, Vector3f.ZERO);
  }

  @Override
  public List<TerrainElement> getTerrainElements(final Vector3f thePlayerLocation, final Vector3f thePlayerDirection) {
    incrementHorizon();
    final List<TerrainElement> elements = new ArrayList<TerrainElement>(getHorizon() * getHorizon() * getHorizon());
    final int playerX = (int) thePlayerLocation.x;
    final int playerY = (int) thePlayerLocation.y;
    final int playerZ = (int) thePlayerLocation.z;
    for (int x = playerX - getHorizon() + HALF_CHUNK_SIZE; x < playerX + getHorizon() + HALF_CHUNK_SIZE; x++) {
      for (int y = playerY - getHorizon() + HALF_CHUNK_SIZE; y < playerY + getHorizon() + HALF_CHUNK_SIZE; y++) {
        for (int z = playerZ - getHorizon() + HALF_CHUNK_SIZE; z < playerZ + getHorizon() + HALF_CHUNK_SIZE; z++) {
          if (!checkBorders(x, y, z)) {
            continue;
          }
          final TerrainElement element = terrainElements[x][y][z];
          if (element == null) {
            // element = createElement(x - HALF_CHUNK_SIZE, y - HALF_CHUNK_SIZE, z - HALF_CHUNK_SIZE);
            // terrainElements[x][y][z] = element;
            continue;
          }
          element.setInPhysicalSpacePresent(isInPhysicalRange(element, thePlayerLocation));
          if (element != EMPTY_TERRAIN_ELEMENT) {
            elements.add(element);
          }
        }
      }
    }
    return elements;
  }

  private void incrementHorizon() {
    if (horizon < END_HORIZON) {
      horizon += 1f;
      System.out.println(String.format("Increase horizon: %f", horizon));
    }
  }

  private boolean isInPhysicalRange(final TerrainElement theElement, final Vector3f thePlayerLocation) {
    return theElement.getCenter().subtract(thePlayerLocation).lengthSquared() < 9;
  }

  @Override
  public void removeTerrainElement(final Vector3f theCenterVector) {
    elementCount--;
    terrainElements[(int) theCenterVector.x + HALF_CHUNK_SIZE][(int) theCenterVector.y + HALF_CHUNK_SIZE][(int) theCenterVector.z
        + HALF_CHUNK_SIZE] = EMPTY_TERRAIN_ELEMENT;
  }
}
